#pragma once
#include <GL/glew.h>
#include <functional>
#include <iostream>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>

namespace Umikaze::Core {

class GlState {
public:
  void bindFramebuffer(uint32_t fbo) {
    if (fbo != _currentFbo) {
      glBindFramebuffer(GL_FRAMEBUFFER, fbo);
      _currentFbo = fbo;
    }
  }

  void disable(GLenum state) {
    auto &member = getMember(state);
    setState(state, false, member);
  }

  void enable(GLenum state) {
    auto &member = getMember(state);
    setState(state, true, member);
  }

  void cull(GLenum cullMode) {
    if (cullMode != _currentCullMode) {
      glCullFace(cullMode);
      _currentCullMode = cullMode;
    }
  }

  void viewport(int x, int y, int width, int height) {
    if (x != _currentViewport[0] || y != _currentViewport[1] ||
        width != _currentViewport[2] || height != _currentViewport[3]) {
      glViewport(x, y, width, height);
      _currentViewport[0] = x;
      _currentViewport[1] = y;
      _currentViewport[2] = width;
      _currentViewport[3] = height;
    }
  }

  void bindVertexArray(uint32_t vao) {
    if (vao != _currentVao) {
      glBindVertexArray(vao);
      _currentVao = vao;
    }
  }

  void bindUniformBuffer(uint32_t ubo) {
    if (ubo != _currentUbo) {
      glBindBuffer(GL_UNIFORM_BUFFER, ubo);
      _currentUbo = ubo;
    }
  }

  void bindArrayBuffer(uint32_t vbo) {
    if (vbo != _currentVbo) {
      glBindBuffer(GL_ARRAY_BUFFER, vbo);
      _currentVbo = vbo;
    }
  }

  void bindReadFrameBuffer(uint32_t fbo) {
    if (fbo != _currentReadFbo) {
      glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
      _currentReadFbo = fbo;
    }
  }

  void bindDrawFrameBuffer(uint32_t fbo) {
    if (fbo != _currentDrawFbo) {
      glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
      _currentDrawFbo = fbo;
    }
  }

  void bindTexture(uint32_t textureId, GLenum format) {
    if (auto iter =
            _currentTextureIds.find({_currentActiveTextureUnit, format});
        iter == _currentTextureIds.end()) {
      glBindTexture(format, textureId);
      _currentTextureIds.insert(
          {{_currentActiveTextureUnit, format}, textureId});
    } else if (iter->second != textureId) {
      glBindTexture(format, textureId);
      iter->second = textureId;
    }
  }

  void activeTexture(uint32_t location) {
    if (_currentActiveTextureUnit != location) {
      glActiveTexture(GL_TEXTURE0 + location);
      _currentActiveTextureUnit = location;
    }
  }

  void useProgram(uint32_t programId) {
    if (programId != _currentProgramId) {
      glUseProgram(programId);
      _currentProgramId = programId;
    }
  }

  void setDepthFunc(GLenum func) {
    if (func != _currentDepthFunc) {
      glDepthFunc(func);
      _currentDepthFunc = func;
    }
  }

  static const std::unique_ptr<GlState> &instance() {
    if (!_instance) {
      _instance = std::unique_ptr<GlState>(new GlState());
    }
    return _instance;
  }

private:
  GlState() {}

  void setState(GLenum state, bool enable, int &currentEnable) {
    if (enable != currentEnable) {
      enable ? glEnable(state) : glDisable(state);
      currentEnable = enable;
    }
  }

  int &getMember(GLenum state) {
    switch (state) {
    case GL_DEPTH_TEST:
      return _isEnableDepthTest;
    case GL_STENCIL_TEST:
      return _isEnableStencilTest;
    case GL_CULL_FACE:
      return _isEnableCullFace;
    default:
      std::cerr << "state 没有" << std::endl;
      break;
    }
  }

  static std::unique_ptr<GlState> _instance;

  uint32_t _currentFbo = 0;
  uint32_t _currentVao = 0;
  uint32_t _currentVbo = 0;
  uint32_t _currentDrawFbo = 0;
  uint32_t _currentReadFbo = 0;
  uint32_t _currentUbo = 0;
  uint32_t _currentProgramId = 0;

  uint32_t _currentTexture2DId = 0;
  uint32_t _currentTextureCubeMapId = 0;
  uint32_t _currentActiveTextureUnit = 0;

  //报错太多了，后续再想办法把key换成一个
  struct Key {
    uint32_t _unit;
    uint32_t _target;

    bool operator==(const Key &other) const {
      return _unit == other._unit && _target == other._target;
    }
  };

  struct KeyHasher {
    std::size_t operator()(const Key &k) const {
      return ((std::hash<uint32_t>()(k._unit) ^
               (std::hash<uint32_t>()(k._target) << 1)) >>
              1);
    }
  };

  std::unordered_map<Key, uint32_t, KeyHasher> _currentTextureIds;

  int _currentDepthFunc = GL_LESS;

  int _currentViewport[4] = {0};
  int _currentCullMode = GL_BACK;
  int _isEnableDepthTest = GL_FALSE;
  int _isEnableStencilTest = GL_FALSE;
  int _isEnableCullFace = GL_FALSE;

private:
};

} // namespace Umikaze::Core
